'''
pygeom -- Python Geometric Engine
constraint -- Constraint Engine

Powers the constraint engine.

The constraint engine is a set of protocols and code that allows
GEs to be "constrained" to each other (which means that they are
forced into a certain position). Every constraint reduces the
degrees of freedom of the GE.

There are three operations the GE must support to satisfy the
constraint protocol:
-GE._ConSetup(self): Sets up the constraints by telling the
 system how this GE is capable of constraint.
-GE._Force<constraint>(self, ge): Forces GE to obey the
 constraint <constraint> relative to the ge parameter.
-GE._Check<constraint>(self, ge): Asks the GE if it obeys
 <constraint> relative to the ge parameter.
-GE.ConTable: A list or dict, indexable by constraint code,
 that holds ConSyntax objects.
-GE.ConValid: True if this GE can be constrained (and generally
 True by default).
As a convenience, the function Check here will take a
constraint constant and a GE (along with the other parameters
needed by the constraint) and check if it satisfies the
constraint.

To make a constraint, create a Constraint object, with a
similar constructor syntax to Check. If you want to make
a World obey it, you should register it with the World.

To make Constraint objects compatible with certain features
of the World, Constraint inherits from ge.GE but does not
extend any geometrical parts of it. DO NOT USE THIS AS AN
EXCUSE TO PERFORM CLASS CHECKS OR OTHER "STANDARD" GE
CHECKS ON IT.
'''

from pygeom import cls
from pygeom import ge

class CON:
    NULL=0
    COINCIDENT=1
    COLLINEAR=2
    PARALLEL=3
    PERPENDICULAR=4
    FIX=5

CON_NAME=['Null', 'Coincident', 'Collinear', 'Parallel',
          'Perpendicular', 'Fix']

class ConSyntax(object):
    def __init__(self, forcefunc, checkfunc, synlist):
        self.forcefunc=forcefunc
        self.checkfunc=checkfunc
        self.synlist=synlist
    @property
    def Check(self):
        return self.checkfunc
    @property
    def Force(self):
        return self.forcefunc
    def CheckSyntax(self, *args):
        for possible in self.synlist:
            for index, klass in enumerate(possible):
                if not cls.Check(args[index].Class, klass):
                    break
            else:
                return True
        return False

def GetValidConSyntax(con, ge, *args):
    if not ge.ConValid:
        raise NotImplementedError('GE %r does not support constraints'%(ge))
    try:
        consyn=ge.ConTable[con]
    except (IndexError, KeyError):
        return NotImplemented
    if consyn is NotImplemented:
        return consyn
    if not consyn.CheckSyntax(*args):
        raise ValueError('GE %r does not support syntax %r'%(ge, args))
    return consyn

def Check(con, ge, *args):
    consyn=GetValidConSyntax(con, ge, *args)
    if consyn is NotImplemented:
        return consyn
    return consyn.Check(*args)

class Constraint(ge.GE):
    def __init__(self, con, ge, *args, init_force=True):
        self.consyn=GetValidConSyntax(con, ge, *args)
        self.coargs=(ge,)+args
        self.con=con
        if self.consyn is NotImplemented:
            raise TypeError('GE %r does not support constraint %s'%(ge, CON_NAME[con]))
        if init_force:
            self.consyn.Force(*self.coargs)
    def __repr__(self):
        return 'Constraint(%s, %r, %r)'%(CON_NAME[self.con], self.coargs[0], self.coargs[1:])
    def Update(self):
        return self.consyn.Force(*self.coargs)
    def _WorldUpdate(self):
        if not self.Update():
            raise ValueError('Constraint %r failed to update (check system)'%(self))
    def _ConSetup(self):
        self._ConSetInvalid(True)
    def _IsctSetup(self):
        self._IsctMode(intersect.NOT_VALID)
